
#include "filedb.h"

#include <string.h>
#include <assert.h>

#include "common/alloc.h"
#include "common/utf8.h"

// intermediate buffer for utf8 -to- ucs4 conversion
#define MAX_UCS4_BUF_SIZE   (256)

// intermediate buffer for ucs4 -to- utf8 conversion (can include all the possible ucs4-content of MAX_UCS4_BUF_SIZE)
#define MAX_UTF8_BUF_SIZE   (4 * MAX_UCS4_BUF_SIZE)

struct FileDb
{
    struct FileTreeHeader   tree_header;
    char *                  tree_content;

    // intermediate buffer for utf8-to-ucs4 conversions of MAX_UCS4_BUF_SIZE
    uint32_t *              ucs4_buf;

    // for convenient implementation of the recursive search
    uint32_t *              ucs4_buf_end;

    // intermediate buffer for ucs4-to-utf8 conversions of MAX_UTF8_BUF_SIZE
    uint8_t *               utf8_buf;

    ResultReceiver          receiver;
};

struct FileDb * load_file_db(FILE * fsrc, ResultReceiver receiver)
{
    struct FileDb * db = xmalloc(sizeof(struct FileDb));
    memset(db, 0, sizeof(struct FileDb));
    bool failed = false;


    // read tree header
    size_t size = fread(&db->tree_header, 1, sizeof(struct FileTreeHeader), fsrc);
    if (size != sizeof(struct FileTreeHeader))
    {
        assert(!"can't read header");
        failed = true;
    }

    // match tree header magic
    if (!failed && (db->tree_header.magic != FILE_TREE_HEADER_MAGIC))
    {
        assert(!"magic number mismatched");
        failed = true;
    }

    // read tree content
    if (!failed)
    {
        db->tree_content = xmalloc(db->tree_header.tree_size);
        size = fread(db->tree_content, 1, db->tree_header.tree_size, fsrc);
        if (size != (size_t)db->tree_header.tree_size)
        {
            assert(!"tree is not of the expected size");
            failed = true;
        }
    }

    // allocate intermediate buffers
    if (!failed)
    {
        db->receiver = receiver;
        db->ucs4_buf = xmalloc(MAX_UCS4_BUF_SIZE * sizeof(uint32_t));
        db->utf8_buf = xmalloc(MAX_UTF8_BUF_SIZE * sizeof(uint8_t));
    }

    // clear db if loading failed
    if (failed)
    {
        free_file_db(db);
        db = NULL;
    }

    return db;
}

void free_file_db(struct FileDb * db)
{
    if (db)
    {
        xfree(db->tree_content);
        xfree(db);
    }
}

// instantiate binary search
#define BSEARCH_NS(name)        entry_##name
#define BSEARCH_ARRAY_TYPE      struct FileEntry *
#define BSEARCH_KEY_TYPE        uint32_t
#define BSEARCH_3W_COMPARE(array, index, key) \
    (key > array[index].character ? 1 : (key < array[index].character ? -1 : 0))

#include <templates/bsearch.h>


static bool recursive_query(struct FileDb * db, FileOffset node_offset, uint32_t * ucs4_buf, uint8_t * resultbuf)
{
    bool ok = true;

    // get node and entries pointers
    struct FileNode * node = (struct FileNode *)(db->tree_content + node_offset);
    struct FileEntry * entries = (struct FileEntry *)(db->tree_content + node_offset + sizeof(struct FileNode));
    if ((char *)(entries + node->entry_count) > (char *)(db->tree_content + db->tree_header.tree_size))
    {
        fprintf(stderr, "error: invalid node offset\n");
        ok = false;
    }

    if (ok)
    {
        // query string is over?
        if (ucs4_buf == db->ucs4_buf_end)
        {
            if (node->entry_count > 0)
            {
                FileInt i;

                // we still have entries - continue recursive search
                for (i = 0; i < node->entry_count; ++i)
                {
                    struct FileEntry * entry = entries + i;
                    uint32_t character = entry->character;
                    size_t utf8_buf_len = (db->utf8_buf + MAX_UTF8_BUF_SIZE - resultbuf);
                    assert(utf8_buf_len > sizeof(uint32_t));

                    int error = utf8_from_ucs4(character, &resultbuf, &utf8_buf_len);
                    if (error != UTF_STATUS_OK)
                    {
                        fprintf(stderr, "error: utf8 conversion error: %d\n", error);
                        ok = false;
                        break;
                    }

                    ok = recursive_query(db, entry->node_offset, ucs4_buf, resultbuf);
                    if (!ok)
                    {
                        break;
                    }
                }
            }
            else
            {
                // this is the leaf node
                *resultbuf = 0;
                ok = db->receiver((char *)db->utf8_buf);
            }
        }
        else
        {
            // query string is not over
            uint32_t character = *ucs4_buf++;

            // get corresponding entry index
            int entry_index = entry_binary_search(entries, node->entry_count, character);
            if (ok && entry_index >= 0)
            {
                // convert character to utf8 part
                size_t utf8_buf_len = (db->utf8_buf + MAX_UTF8_BUF_SIZE - resultbuf);
                assert(utf8_buf_len > sizeof(uint32_t));
                int error = utf8_from_ucs4(character, &resultbuf, &utf8_buf_len);

                if (error != UTF_STATUS_OK)
                {
                    fprintf(stderr, "error: utf8 conversion error: %d\n", error);
                    ok = false;
                }

                // go further
                if (ok)
                {
                    ok = recursive_query(db, entries[entry_index].node_offset, ucs4_buf, resultbuf);
                }
            }
        }
    }

    return ok;
}

bool file_db_query(struct FileDb * db, const char * query)
{
    size_t query_len = strlen(query);
    bool ok = true;

    if (query_len == 0)
    {
        // no need to search
        ok = false;
    }

    // buffer check
    if (ok && query_len >= MAX_UTF8_BUF_SIZE)
    {
        fprintf(stderr, "error: buffer exceeded\n");
        ok = false;
    }

    // convert check
    if (ok)
    {
        db->ucs4_buf_end = utf8_to_ucs4_nocheck((uint8_t *)query, (uint8_t *)query + query_len, db->ucs4_buf);
        if (db->ucs4_buf_end == NULL)
        {
            fprintf(stderr, "error: invalid utf-8 sequence\n");
            ok = false;
        }
    }

    // all to lowercase
    if (ok)
    {
        ucs4_tolower(db->ucs4_buf, db->ucs4_buf_end);
    }

    // get node
    if (ok)
    {
        ok = recursive_query(db, db->tree_header.root_node_offset, db->ucs4_buf, db->utf8_buf);
    }

    return ok;
}
